﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace StableDiffusionTools.Progress
{
    public class Throttler<T> : IDisposable
    {
        private readonly object locker = new object();
        private DateTime lastUpdate = DateTime.MinValue;
        private volatile bool hasState = false;
        private T state;
        private System.Threading.Timer timer;
        private SynchronizationContext context;

        public int Period { get; set; }

        public event ThrottlerEvent<T> Update;
        protected virtual void OnUpdate(ThrottlerEventArgs<T> e)
        {
            var tUpdate = Update;
            if (tUpdate != null)
            {
                if (context == null) tUpdate(this, e);
                else context.Post(delegate { tUpdate(this, e); }, null);
            }
        }
   
        public Throttler(int period = 250, bool useSynchronization = true)
        {
            Period = period;
            timer = new System.Threading.Timer(Timer_Tick, null, Timeout.Infinite, Timeout.Infinite);
            if(useSynchronization) context = SynchronizationContext.Current;
        }

        private void Timer_Tick(object s)
        {
            lock (locker)
            {
                if (hasState)
                {
                    hasState = false;
                    DateTime now = DateTime.Now;
                    TimeSpan delta = now.Subtract(lastUpdate);
                    lastUpdate = now;
                    OnUpdate(new ThrottlerEventArgs<T>(delta, state));
                }
            }
        }

        public void PushState(T newState)
        {
            lock (locker)
            {
                state = newState;

                DateTime now = DateTime.Now;
                if (lastUpdate == DateTime.MinValue)
                    lastUpdate = now;

                TimeSpan delta = now.Subtract(lastUpdate);
                int deltaMiliSec = (int)delta.TotalMilliseconds;
                if (deltaMiliSec >= Period)
                {
                    hasState = false;
                    lastUpdate = now;
                    OnUpdate(new ThrottlerEventArgs<T>(delta, state));
                    timer.Change(Period, Timeout.Infinite);
                }
                else
                {
                    hasState = true;
                    int dueTime = Period - deltaMiliSec;
                    timer.Change(dueTime, Timeout.Infinite);
                }
            }
        }

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                    if (timer != null)
                    {
                        timer.Dispose();
                        timer = null;
                    }
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~Throttler() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
